#ifndef GNLSHADER_H
#define GNLSHADER_H

/*TODO/FIX

-When call useFixedProgram before other program resulting ilegal operation

*/


#include <GL/glew.h>
#include <stdio.h>
#include <stdlib.h>
#include <fstream>
#include <iostream>
#include "gnlVector3.h"

using namespace std;

/*
	Utilized in class development http://www.opengl.org/sdk/docs/man/
*/
class gnlShaderManager;

class gnlShader{
private:
	GLuint shaderId; //Its shader Id generated after compiling 
	int type;

public:
	friend class gnlShaderManager;
	
};


class gnlShaderProgram{

	private:
		GLuint programId;
		gnlShader *vertex;
		gnlShader *fragment;

	public:
	friend class gnlShaderManager;

};


class gnlShaderManager{
	
	private:



		

		static char* readFile (const char* const fileName){

			char *srcText = NULL;
			ifstream fs(fileName, ios::binary); //When add ios::in srcText comming with strange chars at end

			if(!fs.is_open()){
				cout<<"I/O ERROR - Shader not exists!"<<endl; 
				return NULL;
			}

			fs.seekg(0, ios::end);
			int fileSize = fs.tellg();
			fs.seekg(0, ios::beg);

			srcText = new char [(fileSize+1)*sizeof(char)];
			
			fs.read(srcText, fileSize);
			srcText[fileSize] = '\0';
			fs.close();


			return srcText;
		}
	
	public:
		
		enum{
			GNL_VERTEX, GNL_FRAGMENT
		};

		gnlShaderManager(){}	
		
		static void setAttribute(const gnlShaderProgram* const program, unsigned int varLocation,  const gnlVector3 &pos){
			glVertexAttrib3f(varLocation, pos.x, pos.y, pos.z);
		}

		static void setAttribute(const gnlShaderProgram* const program, unsigned int varLocation , const float* const value){
			glVertexAttrib3f(varLocation, value[0], value[1], value[2]);
		}
		
		static unsigned int getAttributeLocation(const gnlShaderProgram* const program, const char * const varName){
			return glGetAttribLocation(program->programId, varName);
		}
	

		 static void setUniform( const gnlShaderProgram* const program, const char *varName, const float * const value){
			
			unsigned int varLocationId = glGetUniformLocation(program->programId, varName);
			glUniform3f(varLocationId, value[0], value[1], value[2]);
		}


	    static void setUniform(const gnlShaderProgram* const program, const char *varName, int value){
			
  			unsigned int varLocationId = glGetUniformLocation(program->programId, varName);

			glUniform1i(varLocationId, value);


		}

		static bool initialize(){
			
			GLenum err = glewInit();

			if (GLEW_OK != err)	{
				return false;
			}

			if (!glewIsSupported("GL_VERSION_2_0")){
				return false;
		    }
		
		}

		static void useFixedProgram(){
			glUseProgram(0);
		}

		static void useProgram(const gnlShaderProgram* const program){
			glUseProgram(program->programId);
		}


		static void destroyProgram(gnlShaderProgram *program){
			 glDetachShader(program->programId,program->vertex->shaderId);
			 glDetachShader(program->programId,program->fragment->shaderId);
			 glDeleteShader(program->vertex->shaderId); 
			 glDeleteShader(program->fragment->shaderId); 
			 glDeleteProgram(program->programId);
		}

		static gnlShaderProgram* createProgram(gnlShader *vertexShader, gnlShader *fragmentShader){
			
			GLuint programId = 0;
			gnlShaderProgram *program;
			int status = 0;
			
			if( !(programId = glCreateProgram()) )
				return NULL;
				

			glAttachShader(programId, vertexShader->shaderId);
			glAttachShader(programId, fragmentShader->shaderId);
					
			glLinkProgram(programId);
			
			
			glGetProgramiv(programId, GL_LINK_STATUS, &status );
			
			if(status == GL_FALSE)
				return NULL;

			program = new gnlShaderProgram();

			program->programId = programId;
			program->vertex = vertexShader;
			program->fragment = fragmentShader;

			return program;
		
		}

		static gnlShader* createShader( const char* const shaderPath, int shaderType ){
			
			GLuint shaderId = 0;

			const char *sourceCode = NULL;
			gnlShader *shader = NULL;

			if( !(sourceCode = readFile(shaderPath)) )
				return NULL;

			switch(shaderType){
				case GNL_VERTEX:
					if( !(shaderId = glCreateShader(GL_VERTEX_SHADER)))
						return NULL;
				break;

				case GNL_FRAGMENT:
					if( !(shaderId = glCreateShader(GL_FRAGMENT_SHADER)))
						return NULL;
				break;

				default:
					return NULL;
				break;
			}
			
			glShaderSource(shaderId, 1, &sourceCode, NULL);
			
			glCompileShader(shaderId);

			GLint status = 0;
			
			glGetShaderiv(shaderId,GL_COMPILE_STATUS, &status);
			
			if( status == GL_FALSE)
				return NULL;

			shader = new gnlShader();

			if(!shader)
				return NULL;

			shader->shaderId = shaderId;
			shader->type = shaderType;

			delete[] sourceCode;
			
			return shader;
		}
		

	/* Not supported in this formar because at any error shader/program are lost by NULL pointer
	static void shaderLog(gnlShader *shader){
	    int infologLength = 0;
	    int charsWritten  = 0;
	    char *infoLog;

		glGetShaderiv(shader->shaderId, GL_INFO_LOG_LENGTH,&infologLength);

	    if (infologLength > 0)
	    {
	        infoLog = new char [infologLength*sizeof(char)];
	        glGetShaderInfoLog(shader->shaderId, infologLength, &charsWritten, infoLog);
			cout<<infoLog<<endl;
	        delete[] infoLog;
	    }
	}

	static void programLog(gnlShaderProgram *program)
	{
	    int infologLength = 0;
	    int charsWritten  = 0;
	    char *infoLog;

		glGetProgramiv(program->programId, GL_INFO_LOG_LENGTH,&infologLength);

	    if (infologLength > 0)
	    {
	        infoLog = new char[infologLength*sizeof(char)];
	        glGetProgramInfoLog(program->programId, infologLength, &charsWritten, infoLog);
			cout<<infoLog<<endl;
	        delete[] infoLog;
	    }
	}

	*/

		
		

};






#endif